package com.springboot.file.ftp;


import com.springboot.file.FileException;
import com.springboot.file.filebase.IFileStorageInterface;
import com.springboot.file.filedto.MultipartFileDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.http.client.utils.DateUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.UUID;

@Slf4j
@Component
public class FTPFileStorageService implements IFileStorageInterface {

    @Value("${ftp.host:test}")
    private String host;
    @Value("${ftp.username:test}")
    private String username;
    @Value("${ftp.password:test}")
    private String password;
    @Value("${ftp.port:1}")
    private int port;
    @Value("${ftp.mkdir:test}")
    private String mkdir;
    @Value("${ftp.myFileUrl:test}")
    private String myFileUrl;

    private final static String ERROR_NO_FILE = "文件不存在!";
    private final static String ERROR_FILE = "FTP文件下载失败!";
    private final static String ERROR_CONN = "连接FTP失败!";


    @Override
    public String uploadMultipartFile(MultipartFileDto multipartFileDto) throws FileException {
        MultipartFile multipartFile = multipartFileDto.getMultipartFile();
        String dictionary = multipartFileDto.getDictionary();
        if (StringUtils.isEmpty(dictionary)) {
            dictionary = DateUtils.formatDate(new Date(), mkdir);
        }
        String path = null;
        try {
            //1、获取原文件后缀名
            String originalFileName = multipartFile.getOriginalFilename();
            String suffix = originalFileName.substring(originalFileName.lastIndexOf('.'));

            //2、使用UUID生成新文件名
            String newFileName = UUID.randomUUID() + suffix;

            //3、将MultipartFile转化为File
            File file = FileCommonUtil.multipartFileToFile(multipartFile);

            //4、上传至ftp服务器
            if (this.uploadToFtp(dictionary, newFileName, file)) {
                log.info("上传至ftp服务器！");
                path = dictionary + newFileName;
            } else {
                throw new FileException("上传至ftp服务器失败!");
            }

        } catch (IOException e) {
            throw new FileException("上传单个文件失败");
        } catch (FileException e) {
            log.error("上传单个文件失败", e);
            throw e;
        }
        return path;
    }

    @Override
    public byte[] downloadFile(String filePath) throws FileException {
        FTPClient ftpClient = this.connectFtp();
        InputStream inputStream = null;
        try {
            Assert.isTrue(existFile(mkdir+filePath, ftpClient), ERROR_NO_FILE);
            String fileName = getFileName(filePath);
            // 此句代码适用Linux的FTP服务器
            String newPath = new String(fileName.getBytes("GBK"), StandardCharsets.ISO_8859_1);
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            changeWorkingDirectory(getMkdir(mkdir+filePath), ftpClient);
            ftpClient.configure(new FTPClientConfig("com.springboot.file.ftp.UnixFTPEntryParser"));
            inputStream = ftpClient.retrieveFileStream(newPath);
            return IOUtils.toByteArray(inputStream);
        } catch (Exception e) {
            log.error("filePath:{}" ,mkdir+filePath);
            log.error("FTP文件下载失败！" ,e);
            Assert.isTrue(false, ERROR_FILE + e.getMessage());
            throw  new FileException(ERROR_FILE);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                    //需要注意，在使用ftpClient.retrieveFileStream,一定要加completePendingCommand（）
                    //调用这个接口后，一定要手动close掉返回的InputStream，然后再调用completePendingCommand方法，若不是按照这个顺序，则会导致后面对FTPClient的操作都失败
                    ftpClient.completePendingCommand();
                } catch (IOException e) {
                    log.error("IOException inputStream", e);
                }
            }
            this.closeFtpClient(ftpClient);
        }
    }

    @Override
    public boolean deleteFile(String filePath) throws FileException {
        // 1、创建FTPClient对象
        FTPClient ftpClient = this.connectFtp();
        try {
            ftpClient.deleteFile(filePath);
            return true;
        } catch (IOException e) {
            throw new FileException("删除文件失败", e);
        }finally{
            try {
                if(ftpClient.isConnected()){
                    ftpClient.disconnect();
                }
            } catch (Exception e) {
                throw new FileException("删除文件 关闭失败", e);
            }
        }
    }

    @Override
    public String getFileLink(String filePath) throws FileException {
        return myFileUrl+filePath;
    }

    @Override
    public String getDomain() {
        return myFileUrl;
    }

    /**
     * 方法描述 从路径中获取文件夹路径
     *
     * @param
     * @since: 1.0
     * @return: String
     * @author: zyj
     * @date: 2023/3/29 13:33
     */
    public String getMkdir(String filePath) {
        String[] split = filePath.split("/");
        //最后一个就是文件名
        String fileName = split[split.length - 1];
        //将文件名从文件路径中用空字符串给替掉，就相当于去掉了
        String newFilePath = filePath.replace(fileName, "");
        return newFilePath;
    }

    /**
     * 方法描述 从路径中获取文件名称
     *
     * @param filePath
     * @since: 1.0
     * @return: String
     * @author: zyj
     * @date: 2023/3/29 13:36
     */
    public String getFileName(String filePath) {
        String[] split = filePath.split("/");
        //最后一个就是文件名
        String fileName = split[split.length - 1];
        return fileName;
    }

    /**
     * 登陆FTP并获取FTPClient对象
     *
     * @return
     */
    public FTPClient connectFtp() throws FileException {
        FTPClient ftpClient = null;
        try {
            ftpClient = new FTPClient();
            //设置连接超时时间
            ftpClient.setConnectTimeout(1000 * 30);
            // 连接FTP服务器
            if (port == 0) {
                ftpClient.connect(host);
            } else {
                ftpClient.connect(host, port);
            }
            // 登陆FTP服务器
            ftpClient.login(username, password);
            // 关键：旧版本用 enterLocalActiveMode() 启用主动模式（替代 setPassive(false)）
//            ftpClient.enterLocalActiveMode();
            // 中文支持
            ftpClient.setControlEncoding("UTF-8");
            // 设置文件类型为二进制（如果从FTP下载或上传的文件是压缩文件的时候，不进行该设置可能会导致获取的压缩文件解压失败）
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
                ftpClient.disconnect();
                Assert.isTrue(false, ERROR_CONN + "用户名或密码错误");
            } else {
                log.info("FTP连接成功!");
            }
        } catch (Exception e) {
            Assert.isTrue(false, ERROR_CONN + e.getMessage());
            throw new FileException("登陆FTP失败，请检查FTP相关配置信息是否正确！");
        }

        return ftpClient;
    }

    /**
     * 关闭FTP连接
     *
     * @param ftpClient
     */
    public void closeFtpClient(FTPClient ftpClient) {
        if (ftpClient.isConnected()) {
            try {
                ftpClient.disconnect();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * 判断ftp服务器文件是否存在
     *
     * @param
     * @param path
     * @return
     * @throws IOException
     */
    private boolean existFile(String path, FTPClient ftpClient) throws IOException {
        boolean flag = false;
        try {
            //ftpClient.enterLocalPassiveMode();
            log.debug("Checking file existence: {}", path);
            //由于apache不支持中文语言环境，通过定制类解析中文日期类型
            FTPFile[] ftpFileArr = ftpClient.listFiles(path);
            log.debug("Checking file  result: {}", ftpFileArr.length);
            if (ftpFileArr.length > 0) {
                flag = true;
            }
        }catch (Exception e){
            log.error("existFile", e);
        }

        return flag;
    }

    /**
     * 方法描述 创建文件夹
     *
     * @param dir
     * @since: 1.0
     * @return: boolean
     * @author: zyj
     * @date: 2023/3/24 16:49
     */
    public boolean makeDirectory(String dir, FTPClient ftpClient) {
        boolean flag = true;
        try {
            flag = ftpClient.makeDirectory(dir);
            if (flag) {
                log.info("创建文件夹" + dir + " 成功！");

            } else {
                log.info("创建文件夹" + dir + " 失败！");
            }
        } catch (Exception e) {
            log.error("makeDirectory", e);
        }
        return flag;
    }

    /**
     * 创建多层目录文件，如果有ftp服务器已存在该文件，则不创建，如果无，则创建
     * 上面的创建一个目录，判断服务器文件是否存在，改变目录路径都是为了创建多层目录文件
     */
    public boolean createDirecroty(String remote, FTPClient ftpClient) throws IOException {
        boolean success = true;
        String directory = remote + "/";
        // 如果远程目录不存在，则递归创建远程服务器目录
        if (!directory.equalsIgnoreCase("/")
                && !changeWorkingDirectory(new String(directory), ftpClient)) {
            int start = 0;
            int end = 0;
            if (directory.startsWith("/")) {
                start = 1;
            } else {
                start = 0;
            }
            end = directory.indexOf("/", start);
            String path = "";
            String paths = "";
            while (true) {
                String subDirectory = new String(remote.substring(start, end).getBytes("GBK"), "iso-8859-1");
                path = path + "/" + subDirectory;
                if (!existFile(path, ftpClient)) {
                    if (makeDirectory(subDirectory, ftpClient)) {
                        changeWorkingDirectory(subDirectory, ftpClient);
                    } else {
                        log.info("创建目录[" + subDirectory + "]失败");
                        changeWorkingDirectory(subDirectory, ftpClient);
                    }
                } else {
                    changeWorkingDirectory(subDirectory, ftpClient);
                }

                paths = paths + "/" + subDirectory;
                start = end + 1;
                end = directory.indexOf("/", start);
                // 检查所有目录是否创建完毕
                if (end <= start) {
                    break;
                }
            }
        }
        return success;
    }


    /**
     * 方法描述 改变目录路径
     *
     * @param directory
     * @since: 1.0
     * @return: boolean
     * @author: zyj
     * @date: 2023/3/24 16:44
     */
    public boolean changeWorkingDirectory(String directory, FTPClient ftpClient) throws IOException {
        boolean flag = true;
        try {
            flag = ftpClient.changeWorkingDirectory(directory);
            if (flag) {
                log.info("进入文件夹" + directory + " 成功！");

            } else {
                log.info("进入文件夹" + directory + " 失败！开始创建文件夹");
            }
        } catch (IOException ioe) {
            log.error("changeWorkingDirectory", ioe);
            throw ioe;
        }
        return flag;
    }


    public boolean uploadToFtp(String remotePath, String fileName, File file) throws FileException {
        boolean upload = true;
        FTPClient ftpClient = this.connectFtp();
        if (ftpClient.isConnected()) {
            try {
                this.createDirecroty(remotePath, ftpClient);
                ftpClient.setBufferSize(1024);
                ftpClient.setControlEncoding("UTF-8");
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                ftpClient.enterLocalPassiveMode();

                //上传文件 参数：上传后的文件名，输入流
                upload = ftpClient.storeFile(fileName, new FileInputStream(file));
            } catch (IOException e) {
                throw new FileException("上传文件异常", e);
            } finally {
                try {
                    ftpClient.disconnect();
                } catch (IOException e) {
                    throw new FileException("上传文件异常 关闭失败", e);
                }
            }
        }
        return upload;
    }


}

